library(tidyverse)
abund <- read.table("../../data/E-WADES/depths_M3.tsv", header=T,
                    row.names=1, sep="\t", check.names=F)
meta_sample <- read.table("../../data/E-WADES/meta_sample.tsv", header=T,
                    row.names=1, sep="\t", check.names=F) %>%
  select(Site, Replicate_Bigger, Date) %>%
  rownames_to_column("sample")
taxon <-  read.table("../../data/E-WADES/taxonomy.tsv", header=T,
                    row.names=1, sep="\t", check.names=F)
all(rownames(abund)==meta_sample$sample) &
all(colnames(abund)==rownames(taxon))
[1] TRUE

Remove replicates from abundance matrix

abund <- abund[meta_sample$Replicate_Bigger,]
meta_sample <- meta_sample[meta_sample$Replicate_Bigger,]

Aggregate to Genus Rank

genus_abund <- abund %>% rownames_to_column("sample") %>%
  pivot_longer(-sample, names_to="Species", values_to="count") %>%
  left_join(taxon%>%rownames_to_column("SpeciesID") %>% select(SpeciesID,Genus) %>%
              rename(Species=SpeciesID), by="Species") %>%
  reframe(count=sum(count), .by=c("sample","Genus")) %>%
  pivot_wider(names_from=Genus, values_from=count) %>%
  column_to_rownames("sample")

genus_rel_abund <- data.frame(genus_abund/rowSums(genus_abund))
subset_genus <- colSums(genus_abund) %>% sort %>% tail(10) %>% names
color_genus <- rownames(qualpalr::qualpal(n = length(subset_genus), 
                                          colorspace = list(h = c(0,360),
                                                            s = c(.25,.6),
                                                            l = c(.45,.85)))$RGB) %>%
  stats::setNames(subset_genus)
color_genus <- c(color_genus, "Other"="#A6A6A6")
color_genus
 Trichococcus Psychrobacter  Giesbergeria     Neisseria Aliarcobacter     Comamonas       Ottowia Acinetobacter 
    "#7CB932"     "#4231B6"     "#B43632"     "#BFD1EF"     "#4879A2"     "#D7A2B0"     "#B939A8"     "#CA8F42" 
Pseudomonas_E    Acidovorax         Other 
    "#42B7A1"     "#EDE6C3"     "#A6A6A6" 
data_composition <- genus_rel_abund %>%
  rownames_to_column("sample") %>%
  pivot_longer(-sample, names_to="genus", values_to="relative") %>%
  mutate(genus = if_else(genus %in% subset_genus, genus, "Other")) %>%
  left_join(meta_sample, by="sample") %>%
  filter(Replicate_Bigger) %>%
  reframe(relative=sum(relative), .by=c("sample","genus","Site")) %>%
  group_by(sample) %>%
  mutate(Pseudomonas_E_rel = sum(relative[genus == "Pseudomonas_E"])) %>%
  ungroup() %>%
  arrange(Pseudomonas_E_rel) %>%
  mutate(sample_fct = factor(sample, levels = unique(sample)))

p.composition <- data_composition %>%
  ggplot(aes(x=sample_fct,y=relative,fill=genus)) +
    geom_bar(stat="identity", color=rgb(0,0,0,.25), linewidth=.25) +
    scale_fill_manual(values=color_genus,
                      labels = function(x) paste0("<i>", x, "</i>")) +
    theme_minimal() +
    theme(legend.position="bottom",
          axis.text.x=element_blank(),
          axis.ticks.x=element_blank(),
          legend.text = ggtext::element_markdown()) +
  ylab("Genus\nComposition") +
  labs(fill="Genus") +
  xlab("Samples") +
  ggnewscale::new_scale_fill() +
  geom_tile(data = data_composition,
            aes(x = sample, y = -0.03, fill = Site), 
            height = 0.03, width = 1, inherit.aes = FALSE) 
p.composition

Dimensionality Reduction

# Calculate the distance matrix with bray-curtis and aitchison
dist.bray <- vegan::vegdist(abund, "bray") %>% as.dist
dist.aitc <- abund %>% 
  zCompositions::cmultRepl() %>%
  vegan::vegdist(method = "aitchison") 
No. adjusted imputations:  148 

PCOA

pcoa.bray <- stats::cmdscale(dist.bray, eig=T, add=T)
pcoa.aitc <- stats::cmdscale(dist.aitc, eig=T, add=T)

Plot with Aitchison Distance

centroids.pcoa.aitc <- pcoa.aitc$points %>% as_tibble %>%
  cbind("Site"=meta_sample$Site) %>%
  reframe(V1=mean(V1), 
          V2=mean(V2),
          .by=Site)

varExplained_aitc <- 100 * (pcoa.aitc$eig / sum(pcoa.aitc$eig)) %>%
  round(2)

p.pcoa.aitc <-  pcoa.aitc$points %>% as_tibble %>% 
  cbind("Site"=meta_sample$Site) %>%
  ggplot(aes(x=V1,y=V2, color=Site)) +
  geom_point(size=3) +
  stat_ellipse() +
  geom_point(data=centroids.pcoa.aitc, shape=21, size=5, color="black",
             aes(fill=Site)) +
  theme_minimal() +
  xlab(paste("PCoA1 (~",varExplained_aitc[1],"%)", sep="")) +
  ylab(paste("PCoA2 (~",varExplained_aitc[2],"%)", sep="")) +
  theme(legend.position="bottom") +
  theme(axis.text=element_text(size=10),
        axis.title=element_text(size=12))

p.pcoa.aitc

varExplained_bray <- 100 * (pcoa.bray$eig / sum(pcoa.bray$eig)) %>%
  round(2)

p.pcoa.bray <- pcoa.bray$points %>% as_tibble %>% 
  as_tibble() %>% mutate(Pseudomonas_E=genus_rel_abund[, "Pseudomonas_E"]) %>%
  ggplot(aes(x=V1, y=V2, fill=Pseudomonas_E)) +
    geom_point(size=4, shape = 21, color = "black") +
    theme_minimal() +
    xlab(paste("PCoA1 (~",varExplained_bray[1],"%)", sep="")) +
    ylab(paste("PCoA2 (~",varExplained_bray[2],"%)", sep="")) +
    #scale_color_viridis_c(option = "C") +
     scale_fill_gradient(
      low = "#2C3E50",  
      high = color_genus["Pseudomonas_E"]  # Same color associated to Pseudomonas_E genus
    ) +
    theme(axis.text=element_text(size=10),
          axis.title=element_text(size=12)) +
    theme(legend.position="bottom") +
    labs(fill = expression("Relative Composition of "*italic("Pseudomonas_E")))
p.pcoa.bray

Merge the figures

p1 <- p.composition + guides(fill=guide_legend(ncol=6))

p2 <- ggpubr::ggarrange(p.pcoa.bray + theme(legend.margin = margin(t = 20, b = 10)),
                        p.pcoa.aitc, ncol=2, labels=c("B","C"))

pall <- ggpubr::ggarrange(p1, p2, 
                          ncol=1, labels=c("A",""),
                          heights=c(0.4,0.6))
pall

png(filename="E-WADES_pcoa_metrics.png", width=8000, height=6000, res=600)
pall
dev.off()
null device 
          1 
pdf(file="E-WADES_pcoa_metrics.pdf", width=13.3, height=10)
pall
dev.off()
null device 
          1 

Adonis 2

meta_sample <- genus_rel_abund %>% as_tibble(rownames = "sample") %>%
  select(sample, Pseudomonas_E) %>%
  right_join(meta_sample, by = "sample") %>%
  mutate(City = case_when(
    str_detect(Site, "Copenaghen") ~ "Copenaghen",
    TRUE ~ Site
  ))
vegan::adonis2(dist.aitc ~ City + Pseudomonas_E, data = meta_sample, 
               permutations = 10^3, by = "margin")
Permutation test for adonis under reduced model
Marginal effects of terms
Permutation: free
Number of permutations: 1000

vegan::adonis2(formula = dist.aitc ~ City + Pseudomonas_E, data = meta_sample, permutations = 10^3, by = "margin")
               Df SumOfSqs      R2      F   Pr(>F)    
City            4   265729 0.32571 32.543 0.000999 ***
Pseudomonas_E   1    35103 0.04303 17.196 0.000999 ***
Residual      229   467480 0.57300                    
Total         234   815846 1.00000                    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
vegan::adonis2(dist.bray ~ City + Pseudomonas_E, data = meta_sample, 
               permutations = 10^3, by = "margin")
Permutation test for adonis under reduced model
Marginal effects of terms
Permutation: free
Number of permutations: 1000

vegan::adonis2(formula = dist.bray ~ City + Pseudomonas_E, data = meta_sample, permutations = 10^3, by = "margin")
               Df SumOfSqs      R2      F   Pr(>F)    
City            4    7.833 0.16663 15.177 0.000999 ***
Pseudomonas_E   1    6.299 0.13401 48.822 0.000999 ***
Residual      229   29.548 0.62856                    
Total         234   47.009 1.00000                    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

UMAP

library(uwot)
set.seed(42)
umap.bray <- uwot::umap(as.dist(dist.bray), min_dist = .5)

set.seed(42)
umap.aitc <- uwot::umap(as.dist(dist.aitc), min_dist = .5)
p.umap.aitc <- umap.aitc %>% as_tibble %>% 
  cbind("Site"=meta_sample$Site) %>%
  ggplot(aes(x=V1,y=V2, fill=Site)) +
  geom_point(size=4, shape = 21, alpha = .75) +
  theme_minimal() +
  xlab("UMAP-1") + ylab("UMAP-2") +
  theme(legend.position="bottom") +
  theme(axis.text=element_text(size=10),
        axis.title=element_text(size=12))

p.umap.aitc

p.umap.bray <- umap.bray %>% as_tibble %>% 
    mutate(Pseudomonas_E=genus_rel_abund[, "Pseudomonas_E"]) %>%
    ggplot(aes(x=V1, y=V2, fill=Pseudomonas_E)) +
    geom_point(size=4, shape = 21, color = "black") +
    theme_minimal() +
    xlab("UMAP-1") + ylab("UMAP-2") +
    #scale_color_viridis_c(option = "C") +
     scale_fill_gradient(
      low = "#2C3E50",  
      high = color_genus["Pseudomonas_E"]  # Same color associated to Pseudomonas_E genus
    ) +
    theme(axis.text=element_text(size=10),
          axis.title=element_text(size=12)) +
    theme(legend.position="bottom") +
    labs(fill = expression("Relative Composition of "*italic("Pseudomonas_E")))
p.umap <- ggpubr::ggarrange(
  p.umap.bray + theme(plot.margin = margin(b = 15, t = 5)) + ggtitle("E-WADES Bray-Curtis"), 
  p.umap.aitc + ggtitle("E-WADES Aitchison"), 
  ncol = 2
)
p.umap

png(filename = "../Supplementary/EWADES_umap_metrics.png", width = 3000, height = 1800, res = 300)
p.umap
dev.off()
null device 
          1 

Review

pcoa.JSD <- philentropy::distance(abund, method = "jensen-shannon") %>%
  stats::cmdscale(eig=T, add=T)
Metric: 'jensen-shannon' using unit: 'log'; comparing: 235 vectors.
pcoa.CAN <- vegan::vegdist(abund, method = "canberra") %>%
  stats::cmdscale(eig=T, add=T)
pcoa.HEL <- vegan::vegdist(abund, method = "hellinger") %>%
  stats::cmdscale(eig=T, add=T)
plot.PCOA.pseo <- function(pcoa, meta_sample, genus_abun){
  
  varExplained <- 100 * (pcoa$eig / sum(pcoa$eig)) %>%
    round(2)

  p <- pcoa$points %>% as_tibble %>% 
    as_tibble() %>% mutate(Pseudomonas_E=genus_abun[, "Pseudomonas_E"]) %>%
    ggplot(aes(x=V1, y=V2, color=Pseudomonas_E)) +
    geom_point(size=2) +
    theme_minimal() +
    xlab(paste("PCoA1 (~",varExplained[1],"%)", sep="")) +
    ylab(paste("PCoA2 (~",varExplained[2],"%)", sep="")) +
    scale_fill_gradient(
        low = "#2C3E50",  
        high = color_genus["Pseudomonas_E"]  # Same color associated to Pseudomonas_E genus
      ) +
    theme(legend.position="none") 

  return(p)
}
plot.PCOA.city <- function(pcoa, meta_sample){
  
  varExplained_aitc <- 100 * (pcoa$eig / sum(pcoa$eig)) %>%
    round(2)

  p <-  pcoa$points %>% as_tibble %>% 
    cbind("City" = meta_sample$City) %>%
    ggplot(aes(x=V1,y=V2, color=City)) +
    geom_point(size=2) +
    theme_minimal() +
    xlab(paste("PCoA1 (~",varExplained_aitc[1],"%)", sep="")) +
    ylab(paste("PCoA2 (~",varExplained_aitc[2],"%)", sep="")) +
    theme(legend.position="none") 
}
p.JSD.pseo <- plot.PCOA.pseo(pcoa.JSD, meta_sample, genus_rel_abund)
p.CAN.pseo <- plot.PCOA.pseo(pcoa.CAN, meta_sample, genus_rel_abund)
p.HEL.pseo <- plot.PCOA.pseo(pcoa.HEL, meta_sample, genus_rel_abund)
p.AIT.pseo <- plot.PCOA.pseo(pcoa.aitc, meta_sample, genus_rel_abund)
p.BRA.pseo <- plot.PCOA.pseo(pcoa.bray, meta_sample, genus_rel_abund)
p.JSD.city <- plot.PCOA.city(pcoa.JSD, meta_sample)
p.CAN.city <- plot.PCOA.city(pcoa.CAN, meta_sample)
p.HEL.city <- plot.PCOA.city(pcoa.HEL, meta_sample)
p.AIT.city <- plot.PCOA.city(pcoa.aitc, meta_sample)
p.BRA.city <- plot.PCOA.city(pcoa.bray, meta_sample)
p.pseo <- ggpubr::ggarrange(
  p.JSD.pseo + ggtitle("jensen-shannon pseudomonadaceae"), 
  p.CAN.pseo + ggtitle("canberra pseudomonadaceae"), 
  p.HEL.pseo + ggtitle("hellinger pseudomonadaceae"),
  p.BRA.pseo + ggtitle("bray-curtis pseudomonadaceae"),
  p.AIT.pseo + ggtitle("aitchison pseudomonadaceae"), 
  ncol = 5, legend = "none"
)
p.city <- ggpubr::ggarrange(
  p.JSD.city + ggtitle("jensen-shannon city"), 
  p.CAN.city + ggtitle("canberra city"), 
  p.HEL.city + ggtitle("hellinger city"),
  p.BRA.city + ggtitle("bray-curtis city"),
  p.AIT.city + ggtitle("aitchison city"), 
  ncol = 5, common.legend = T, legend = "none"
)
pall <- ggpubr::ggarrange(p.pseo, p.city, nrow = 2)
pall

png("../Supplementary/EWADES_pcoa_more_metrics.png", width = 6000, height = 2400, res = 300)
pall
dev.off()
null device 
          1 
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpgYGAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQphYnVuZCA8LSByZWFkLnRhYmxlKCIuLi8uLi9kYXRhL0UtV0FERVMvZGVwdGhzX00zLnRzdiIsIGhlYWRlcj1ULAogICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcz0xLCBzZXA9Ilx0IiwgY2hlY2submFtZXM9RikKbWV0YV9zYW1wbGUgPC0gcmVhZC50YWJsZSgiLi4vLi4vZGF0YS9FLVdBREVTL21ldGFfc2FtcGxlLnRzdiIsIGhlYWRlcj1ULAogICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcz0xLCBzZXA9Ilx0IiwgY2hlY2submFtZXM9RikgJT4lCiAgc2VsZWN0KFNpdGUsIFJlcGxpY2F0ZV9CaWdnZXIsIERhdGUpICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigic2FtcGxlIikKdGF4b24gPC0gIHJlYWQudGFibGUoIi4uLy4uL2RhdGEvRS1XQURFUy90YXhvbm9teS50c3YiLCBoZWFkZXI9VCwKICAgICAgICAgICAgICAgICAgICByb3cubmFtZXM9MSwgc2VwPSJcdCIsIGNoZWNrLm5hbWVzPUYpCmBgYAoKYGBge3J9CmFsbChyb3duYW1lcyhhYnVuZCk9PW1ldGFfc2FtcGxlJHNhbXBsZSkgJgphbGwoY29sbmFtZXMoYWJ1bmQpPT1yb3duYW1lcyh0YXhvbikpCmBgYAoKIyMjIFJlbW92ZSByZXBsaWNhdGVzIGZyb20gYWJ1bmRhbmNlIG1hdHJpeAoKYGBge3J9CmFidW5kIDwtIGFidW5kW21ldGFfc2FtcGxlJFJlcGxpY2F0ZV9CaWdnZXIsXQptZXRhX3NhbXBsZSA8LSBtZXRhX3NhbXBsZVttZXRhX3NhbXBsZSRSZXBsaWNhdGVfQmlnZ2VyLF0KYGBgCgoKIyMgQWdncmVnYXRlIHRvIEdlbnVzIFJhbmsKCmBgYHtyfQpnZW51c19hYnVuZCA8LSBhYnVuZCAlPiUgcm93bmFtZXNfdG9fY29sdW1uKCJzYW1wbGUiKSAlPiUKICBwaXZvdF9sb25nZXIoLXNhbXBsZSwgbmFtZXNfdG89IlNwZWNpZXMiLCB2YWx1ZXNfdG89ImNvdW50IikgJT4lCiAgbGVmdF9qb2luKHRheG9uJT4lcm93bmFtZXNfdG9fY29sdW1uKCJTcGVjaWVzSUQiKSAlPiUgc2VsZWN0KFNwZWNpZXNJRCxHZW51cykgJT4lCiAgICAgICAgICAgICAgcmVuYW1lKFNwZWNpZXM9U3BlY2llc0lEKSwgYnk9IlNwZWNpZXMiKSAlPiUKICByZWZyYW1lKGNvdW50PXN1bShjb3VudCksIC5ieT1jKCJzYW1wbGUiLCJHZW51cyIpKSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tPUdlbnVzLCB2YWx1ZXNfZnJvbT1jb3VudCkgJT4lCiAgY29sdW1uX3RvX3Jvd25hbWVzKCJzYW1wbGUiKQoKZ2VudXNfcmVsX2FidW5kIDwtIGRhdGEuZnJhbWUoZ2VudXNfYWJ1bmQvcm93U3VtcyhnZW51c19hYnVuZCkpCmBgYAoKYGBge3J9CnN1YnNldF9nZW51cyA8LSBjb2xTdW1zKGdlbnVzX2FidW5kKSAlPiUgc29ydCAlPiUgdGFpbCgxMCkgJT4lIG5hbWVzCmBgYAoKYGBge3J9CmNvbG9yX2dlbnVzIDwtIHJvd25hbWVzKHF1YWxwYWxyOjpxdWFscGFsKG4gPSBsZW5ndGgoc3Vic2V0X2dlbnVzKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yc3BhY2UgPSBsaXN0KGggPSBjKDAsMzYwKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcyA9IGMoLjI1LC42KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbCA9IGMoLjQ1LC44NSkpKSRSR0IpICU+JQogIHN0YXRzOjpzZXROYW1lcyhzdWJzZXRfZ2VudXMpCmNvbG9yX2dlbnVzIDwtIGMoY29sb3JfZ2VudXMsICJPdGhlciI9IiNBNkE2QTYiKQpjb2xvcl9nZW51cwpgYGAKCmBgYHtyfQpkYXRhX2NvbXBvc2l0aW9uIDwtIGdlbnVzX3JlbF9hYnVuZCAlPiUKICByb3duYW1lc190b19jb2x1bW4oInNhbXBsZSIpICU+JQogIHBpdm90X2xvbmdlcigtc2FtcGxlLCBuYW1lc190bz0iZ2VudXMiLCB2YWx1ZXNfdG89InJlbGF0aXZlIikgJT4lCiAgbXV0YXRlKGdlbnVzID0gaWZfZWxzZShnZW51cyAlaW4lIHN1YnNldF9nZW51cywgZ2VudXMsICJPdGhlciIpKSAlPiUKICBsZWZ0X2pvaW4obWV0YV9zYW1wbGUsIGJ5PSJzYW1wbGUiKSAlPiUKICBmaWx0ZXIoUmVwbGljYXRlX0JpZ2dlcikgJT4lCiAgcmVmcmFtZShyZWxhdGl2ZT1zdW0ocmVsYXRpdmUpLCAuYnk9Yygic2FtcGxlIiwiZ2VudXMiLCJTaXRlIikpICU+JQogIGdyb3VwX2J5KHNhbXBsZSkgJT4lCiAgbXV0YXRlKFBzZXVkb21vbmFzX0VfcmVsID0gc3VtKHJlbGF0aXZlW2dlbnVzID09ICJQc2V1ZG9tb25hc19FIl0pKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgYXJyYW5nZShQc2V1ZG9tb25hc19FX3JlbCkgJT4lCiAgbXV0YXRlKHNhbXBsZV9mY3QgPSBmYWN0b3Ioc2FtcGxlLCBsZXZlbHMgPSB1bmlxdWUoc2FtcGxlKSkpCgpwLmNvbXBvc2l0aW9uIDwtIGRhdGFfY29tcG9zaXRpb24gJT4lCiAgZ2dwbG90KGFlcyh4PXNhbXBsZV9mY3QseT1yZWxhdGl2ZSxmaWxsPWdlbnVzKSkgKwogICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLCBjb2xvcj1yZ2IoMCwwLDAsLjI1KSwgbGluZXdpZHRoPS4yNSkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWNvbG9yX2dlbnVzLAogICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gZnVuY3Rpb24oeCkgcGFzdGUwKCI8aT4iLCB4LCAiPC9pPiIpKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iLAogICAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50aWNrcy54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGxlZ2VuZC50ZXh0ID0gZ2d0ZXh0OjplbGVtZW50X21hcmtkb3duKCkpICsKICB5bGFiKCJHZW51c1xuQ29tcG9zaXRpb24iKSArCiAgbGFicyhmaWxsPSJHZW51cyIpICsKICB4bGFiKCJTYW1wbGVzIikgKwogIGdnbmV3c2NhbGU6Om5ld19zY2FsZV9maWxsKCkgKwogIGdlb21fdGlsZShkYXRhID0gZGF0YV9jb21wb3NpdGlvbiwKICAgICAgICAgICAgYWVzKHggPSBzYW1wbGUsIHkgPSAtMC4wMywgZmlsbCA9IFNpdGUpLCAKICAgICAgICAgICAgaGVpZ2h0ID0gMC4wMywgd2lkdGggPSAxLCBpbmhlcml0LmFlcyA9IEZBTFNFKSAKcC5jb21wb3NpdGlvbgpgYGAKCiMjIERpbWVuc2lvbmFsaXR5IFJlZHVjdGlvbgoKYGBge3J9CiMgQ2FsY3VsYXRlIHRoZSBkaXN0YW5jZSBtYXRyaXggd2l0aCBicmF5LWN1cnRpcyBhbmQgYWl0Y2hpc29uCmRpc3QuYnJheSA8LSB2ZWdhbjo6dmVnZGlzdChhYnVuZCwgImJyYXkiKSAlPiUgYXMuZGlzdApkaXN0LmFpdGMgPC0gYWJ1bmQgJT4lIAogIHpDb21wb3NpdGlvbnM6OmNtdWx0UmVwbCgpICU+JQogIHZlZ2FuOjp2ZWdkaXN0KG1ldGhvZCA9ICJhaXRjaGlzb24iKSAKYGBgCgojIyBQQ09BCgpgYGB7cn0KcGNvYS5icmF5IDwtIHN0YXRzOjpjbWRzY2FsZShkaXN0LmJyYXksIGVpZz1ULCBhZGQ9VCkKcGNvYS5haXRjIDwtIHN0YXRzOjpjbWRzY2FsZShkaXN0LmFpdGMsIGVpZz1ULCBhZGQ9VCkKYGBgCgojIyBQbG90IHdpdGggQWl0Y2hpc29uIERpc3RhbmNlCgpgYGB7ciwgd2FybmluZz1GQUxTRX0KY2VudHJvaWRzLnBjb2EuYWl0YyA8LSBwY29hLmFpdGMkcG9pbnRzICU+JSBhc190aWJibGUgJT4lCiAgY2JpbmQoIlNpdGUiPW1ldGFfc2FtcGxlJFNpdGUpICU+JQogIHJlZnJhbWUoVjE9bWVhbihWMSksIAogICAgICAgICAgVjI9bWVhbihWMiksCiAgICAgICAgICAuYnk9U2l0ZSkKCnZhckV4cGxhaW5lZF9haXRjIDwtIDEwMCAqIChwY29hLmFpdGMkZWlnIC8gc3VtKHBjb2EuYWl0YyRlaWcpKSAlPiUKICByb3VuZCgyKQoKcC5wY29hLmFpdGMgPC0gIHBjb2EuYWl0YyRwb2ludHMgJT4lIGFzX3RpYmJsZSAlPiUgCiAgY2JpbmQoIlNpdGUiPW1ldGFfc2FtcGxlJFNpdGUpICU+JQogIGdncGxvdChhZXMoeD1WMSx5PVYyLCBjb2xvcj1TaXRlKSkgKwogIGdlb21fcG9pbnQoc2l6ZT0zKSArCiAgc3RhdF9lbGxpcHNlKCkgKwogIGdlb21fcG9pbnQoZGF0YT1jZW50cm9pZHMucGNvYS5haXRjLCBzaGFwZT0yMSwgc2l6ZT01LCBjb2xvcj0iYmxhY2siLAogICAgICAgICAgICAgYWVzKGZpbGw9U2l0ZSkpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHhsYWIocGFzdGUoIlBDb0ExICh+Iix2YXJFeHBsYWluZWRfYWl0Y1sxXSwiJSkiLCBzZXA9IiIpKSArCiAgeWxhYihwYXN0ZSgiUENvQTIgKH4iLHZhckV4cGxhaW5lZF9haXRjWzJdLCIlKSIsIHNlcD0iIikpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpICsKICB0aGVtZShheGlzLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTApLAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTIpKQoKcC5wY29hLmFpdGMKYGBgCgpgYGB7cn0KdmFyRXhwbGFpbmVkX2JyYXkgPC0gMTAwICogKHBjb2EuYnJheSRlaWcgLyBzdW0ocGNvYS5icmF5JGVpZykpICU+JQogIHJvdW5kKDIpCgpwLnBjb2EuYnJheSA8LSBwY29hLmJyYXkkcG9pbnRzICU+JSBhc190aWJibGUgJT4lIAogIGFzX3RpYmJsZSgpICU+JSBtdXRhdGUoUHNldWRvbW9uYXNfRT1nZW51c19yZWxfYWJ1bmRbLCAiUHNldWRvbW9uYXNfRSJdKSAlPiUKICBnZ3Bsb3QoYWVzKHg9VjEsIHk9VjIsIGZpbGw9UHNldWRvbW9uYXNfRSkpICsKICAgIGdlb21fcG9pbnQoc2l6ZT00LCBzaGFwZSA9IDIxLCBjb2xvciA9ICJibGFjayIpICsKICAgIHRoZW1lX21pbmltYWwoKSArCiAgICB4bGFiKHBhc3RlKCJQQ29BMSAofiIsdmFyRXhwbGFpbmVkX2JyYXlbMV0sIiUpIiwgc2VwPSIiKSkgKwogICAgeWxhYihwYXN0ZSgiUENvQTIgKH4iLHZhckV4cGxhaW5lZF9icmF5WzJdLCIlKSIsIHNlcD0iIikpICsKICAgICNzY2FsZV9jb2xvcl92aXJpZGlzX2Mob3B0aW9uID0gIkMiKSArCiAgICAgc2NhbGVfZmlsbF9ncmFkaWVudCgKICAgICAgbG93ID0gIiMyQzNFNTAiLCAgCiAgICAgIGhpZ2ggPSBjb2xvcl9nZW51c1siUHNldWRvbW9uYXNfRSJdICAjIFNhbWUgY29sb3IgYXNzb2NpYXRlZCB0byBQc2V1ZG9tb25hc19FIGdlbnVzCiAgICApICsKICAgIHRoZW1lKGF4aXMudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMCksCiAgICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEyKSkgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKSArCiAgICBsYWJzKGZpbGwgPSBleHByZXNzaW9uKCJSZWxhdGl2ZSBDb21wb3NpdGlvbiBvZiAiKml0YWxpYygiUHNldWRvbW9uYXNfRSIpKSkKcC5wY29hLmJyYXkKYGBgCgoKIyMgTWVyZ2UgdGhlIGZpZ3VyZXMKCmBgYHtyLCBmaWcud2lkdGg9MTMuMywgZmlnLmhlaWdodD0xMH0KcDEgPC0gcC5jb21wb3NpdGlvbiArIGd1aWRlcyhmaWxsPWd1aWRlX2xlZ2VuZChuY29sPTYpKQoKcDIgPC0gZ2dwdWJyOjpnZ2FycmFuZ2UocC5wY29hLmJyYXkgKyB0aGVtZShsZWdlbmQubWFyZ2luID0gbWFyZ2luKHQgPSAyMCwgYiA9IDEwKSksCiAgICAgICAgICAgICAgICAgICAgICAgIHAucGNvYS5haXRjLCBuY29sPTIsIGxhYmVscz1jKCJCIiwiQyIpKQoKcGFsbCA8LSBnZ3B1YnI6OmdnYXJyYW5nZShwMSwgcDIsIAogICAgICAgICAgICAgICAgICAgICAgICAgIG5jb2w9MSwgbGFiZWxzPWMoIkEiLCIiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBoZWlnaHRzPWMoMC40LDAuNikpCnBhbGwKYGBgCgoKYGBge3J9CnBuZyhmaWxlbmFtZT0iRS1XQURFU19wY29hX21ldHJpY3MucG5nIiwgd2lkdGg9ODAwMCwgaGVpZ2h0PTYwMDAsIHJlcz02MDApCnBhbGwKZGV2Lm9mZigpCgpwZGYoZmlsZT0iRS1XQURFU19wY29hX21ldHJpY3MucGRmIiwgd2lkdGg9MTMuMywgaGVpZ2h0PTEwKQpwYWxsCmRldi5vZmYoKQpgYGAKCiMjIEFkb25pcyAyCgpgYGB7cn0KbWV0YV9zYW1wbGUgPC0gZ2VudXNfcmVsX2FidW5kICU+JSBhc190aWJibGUocm93bmFtZXMgPSAic2FtcGxlIikgJT4lCiAgc2VsZWN0KHNhbXBsZSwgUHNldWRvbW9uYXNfRSkgJT4lCiAgcmlnaHRfam9pbihtZXRhX3NhbXBsZSwgYnkgPSAic2FtcGxlIikgJT4lCiAgbXV0YXRlKENpdHkgPSBjYXNlX3doZW4oCiAgICBzdHJfZGV0ZWN0KFNpdGUsICJDb3BlbmFnaGVuIikgfiAiQ29wZW5hZ2hlbiIsCiAgICBUUlVFIH4gU2l0ZQogICkpCmBgYAoKYGBge3J9CnZlZ2FuOjphZG9uaXMyKGRpc3QuYWl0YyB+IENpdHkgKyBQc2V1ZG9tb25hc19FLCBkYXRhID0gbWV0YV9zYW1wbGUsIAogICAgICAgICAgICAgICBwZXJtdXRhdGlvbnMgPSAxMF4zLCBieSA9ICJtYXJnaW4iKQpgYGAKCmBgYHtyfQp2ZWdhbjo6YWRvbmlzMihkaXN0LmJyYXkgfiBDaXR5ICsgUHNldWRvbW9uYXNfRSwgZGF0YSA9IG1ldGFfc2FtcGxlLCAKICAgICAgICAgICAgICAgcGVybXV0YXRpb25zID0gMTBeMywgYnkgPSAibWFyZ2luIikKYGBgCgoKIyMgVU1BUAoKYGBge3IsbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeSh1d290KQpzZXQuc2VlZCg0MikKdW1hcC5icmF5IDwtIHV3b3Q6OnVtYXAoYXMuZGlzdChkaXN0LmJyYXkpLCBtaW5fZGlzdCA9IC41KQoKc2V0LnNlZWQoNDIpCnVtYXAuYWl0YyA8LSB1d290Ojp1bWFwKGFzLmRpc3QoZGlzdC5haXRjKSwgbWluX2Rpc3QgPSAuNSkKYGBgCgpgYGB7cn0KcC51bWFwLmFpdGMgPC0gdW1hcC5haXRjICU+JSBhc190aWJibGUgJT4lIAogIGNiaW5kKCJTaXRlIj1tZXRhX3NhbXBsZSRTaXRlKSAlPiUKICBnZ3Bsb3QoYWVzKHg9VjEseT1WMiwgZmlsbD1TaXRlKSkgKwogIGdlb21fcG9pbnQoc2l6ZT00LCBzaGFwZSA9IDIxLCBhbHBoYSA9IC43NSkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgeGxhYigiVU1BUC0xIikgKyB5bGFiKCJVTUFQLTIiKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKSArCiAgdGhlbWUoYXhpcy50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEwKSwKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEyKSkKCnAudW1hcC5haXRjCmBgYAoKYGBge3J9CnAudW1hcC5icmF5IDwtIHVtYXAuYnJheSAlPiUgYXNfdGliYmxlICU+JSAKICAgIG11dGF0ZShQc2V1ZG9tb25hc19FPWdlbnVzX3JlbF9hYnVuZFssICJQc2V1ZG9tb25hc19FIl0pICU+JQogICAgZ2dwbG90KGFlcyh4PVYxLCB5PVYyLCBmaWxsPVBzZXVkb21vbmFzX0UpKSArCiAgICBnZW9tX3BvaW50KHNpemU9NCwgc2hhcGUgPSAyMSwgY29sb3IgPSAiYmxhY2siKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgeGxhYigiVU1BUC0xIikgKyB5bGFiKCJVTUFQLTIiKSArCiAgICAjc2NhbGVfY29sb3JfdmlyaWRpc19jKG9wdGlvbiA9ICJDIikgKwogICAgIHNjYWxlX2ZpbGxfZ3JhZGllbnQoCiAgICAgIGxvdyA9ICIjMkMzRTUwIiwgIAogICAgICBoaWdoID0gY29sb3JfZ2VudXNbIlBzZXVkb21vbmFzX0UiXSAgIyBTYW1lIGNvbG9yIGFzc29jaWF0ZWQgdG8gUHNldWRvbW9uYXNfRSBnZW51cwogICAgKSArCiAgICB0aGVtZShheGlzLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTApLAogICAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMikpICsKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIikgKwogICAgbGFicyhmaWxsID0gZXhwcmVzc2lvbigiUmVsYXRpdmUgQ29tcG9zaXRpb24gb2YgIippdGFsaWMoIlBzZXVkb21vbmFzX0UiKSkpCmBgYAoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD01fQpwLnVtYXAgPC0gZ2dwdWJyOjpnZ2FycmFuZ2UoCiAgcC51bWFwLmJyYXkgKyB0aGVtZShwbG90Lm1hcmdpbiA9IG1hcmdpbihiID0gMTUsIHQgPSA1KSkgKyBnZ3RpdGxlKCJFLVdBREVTIEJyYXktQ3VydGlzIiksIAogIHAudW1hcC5haXRjICsgZ2d0aXRsZSgiRS1XQURFUyBBaXRjaGlzb24iKSwgCiAgbmNvbCA9IDIKKQpwLnVtYXAKYGBgCgpgYGB7cn0KcG5nKGZpbGVuYW1lID0gIi4uL1N1cHBsZW1lbnRhcnkvRVdBREVTX3VtYXBfbWV0cmljcy5wbmciLCB3aWR0aCA9IDMwMDAsIGhlaWdodCA9IDE4MDAsIHJlcyA9IDMwMCkKcC51bWFwCmRldi5vZmYoKQpgYGAKCiMjIFJldmlldwoKYGBge3J9CnBjb2EuSlNEIDwtIHBoaWxlbnRyb3B5OjpkaXN0YW5jZShhYnVuZCwgbWV0aG9kID0gImplbnNlbi1zaGFubm9uIikgJT4lCiAgc3RhdHM6OmNtZHNjYWxlKGVpZz1ULCBhZGQ9VCkKcGNvYS5DQU4gPC0gdmVnYW46OnZlZ2Rpc3QoYWJ1bmQsIG1ldGhvZCA9ICJjYW5iZXJyYSIpICU+JQogIHN0YXRzOjpjbWRzY2FsZShlaWc9VCwgYWRkPVQpCnBjb2EuSEVMIDwtIHZlZ2FuOjp2ZWdkaXN0KGFidW5kLCBtZXRob2QgPSAiaGVsbGluZ2VyIikgJT4lCiAgc3RhdHM6OmNtZHNjYWxlKGVpZz1ULCBhZGQ9VCkKYGBgCgpgYGB7cn0KcGxvdC5QQ09BLnBzZW8gPC0gZnVuY3Rpb24ocGNvYSwgbWV0YV9zYW1wbGUsIGdlbnVzX2FidW4pewogIAogIHZhckV4cGxhaW5lZCA8LSAxMDAgKiAocGNvYSRlaWcgLyBzdW0ocGNvYSRlaWcpKSAlPiUKICAgIHJvdW5kKDIpCgogIHAgPC0gcGNvYSRwb2ludHMgJT4lIGFzX3RpYmJsZSAlPiUgCiAgICBhc190aWJibGUoKSAlPiUgbXV0YXRlKFBzZXVkb21vbmFzX0U9Z2VudXNfYWJ1blssICJQc2V1ZG9tb25hc19FIl0pICU+JQogICAgZ2dwbG90KGFlcyh4PVYxLCB5PVYyLCBjb2xvcj1Qc2V1ZG9tb25hc19FKSkgKwogICAgZ2VvbV9wb2ludChzaXplPTIpICsKICAgIHRoZW1lX21pbmltYWwoKSArCiAgICB4bGFiKHBhc3RlKCJQQ29BMSAofiIsdmFyRXhwbGFpbmVkWzFdLCIlKSIsIHNlcD0iIikpICsKICAgIHlsYWIocGFzdGUoIlBDb0EyICh+Iix2YXJFeHBsYWluZWRbMl0sIiUpIiwgc2VwPSIiKSkgKwogICAgc2NhbGVfZmlsbF9ncmFkaWVudCgKICAgICAgICBsb3cgPSAiIzJDM0U1MCIsICAKICAgICAgICBoaWdoID0gY29sb3JfZ2VudXNbIlBzZXVkb21vbmFzX0UiXSAgIyBTYW1lIGNvbG9yIGFzc29jaWF0ZWQgdG8gUHNldWRvbW9uYXNfRSBnZW51cwogICAgICApICsKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpIAoKICByZXR1cm4ocCkKfQpgYGAKCmBgYHtyfQpwbG90LlBDT0EuY2l0eSA8LSBmdW5jdGlvbihwY29hLCBtZXRhX3NhbXBsZSl7CiAgCiAgdmFyRXhwbGFpbmVkX2FpdGMgPC0gMTAwICogKHBjb2EkZWlnIC8gc3VtKHBjb2EkZWlnKSkgJT4lCiAgICByb3VuZCgyKQoKICBwIDwtICBwY29hJHBvaW50cyAlPiUgYXNfdGliYmxlICU+JSAKICAgIGNiaW5kKCJDaXR5IiA9IG1ldGFfc2FtcGxlJENpdHkpICU+JQogICAgZ2dwbG90KGFlcyh4PVYxLHk9VjIsIGNvbG9yPUNpdHkpKSArCiAgICBnZW9tX3BvaW50KHNpemU9MikgKwogICAgdGhlbWVfbWluaW1hbCgpICsKICAgIHhsYWIocGFzdGUoIlBDb0ExICh+Iix2YXJFeHBsYWluZWRfYWl0Y1sxXSwiJSkiLCBzZXA9IiIpKSArCiAgICB5bGFiKHBhc3RlKCJQQ29BMiAofiIsdmFyRXhwbGFpbmVkX2FpdGNbMl0sIiUpIiwgc2VwPSIiKSkgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikgCn0KYGBgCgoKYGBge3J9CnAuSlNELnBzZW8gPC0gcGxvdC5QQ09BLnBzZW8ocGNvYS5KU0QsIG1ldGFfc2FtcGxlLCBnZW51c19yZWxfYWJ1bmQpCnAuQ0FOLnBzZW8gPC0gcGxvdC5QQ09BLnBzZW8ocGNvYS5DQU4sIG1ldGFfc2FtcGxlLCBnZW51c19yZWxfYWJ1bmQpCnAuSEVMLnBzZW8gPC0gcGxvdC5QQ09BLnBzZW8ocGNvYS5IRUwsIG1ldGFfc2FtcGxlLCBnZW51c19yZWxfYWJ1bmQpCnAuQUlULnBzZW8gPC0gcGxvdC5QQ09BLnBzZW8ocGNvYS5haXRjLCBtZXRhX3NhbXBsZSwgZ2VudXNfcmVsX2FidW5kKQpwLkJSQS5wc2VvIDwtIHBsb3QuUENPQS5wc2VvKHBjb2EuYnJheSwgbWV0YV9zYW1wbGUsIGdlbnVzX3JlbF9hYnVuZCkKYGBgCgpgYGB7cn0KcC5KU0QuY2l0eSA8LSBwbG90LlBDT0EuY2l0eShwY29hLkpTRCwgbWV0YV9zYW1wbGUpCnAuQ0FOLmNpdHkgPC0gcGxvdC5QQ09BLmNpdHkocGNvYS5DQU4sIG1ldGFfc2FtcGxlKQpwLkhFTC5jaXR5IDwtIHBsb3QuUENPQS5jaXR5KHBjb2EuSEVMLCBtZXRhX3NhbXBsZSkKcC5BSVQuY2l0eSA8LSBwbG90LlBDT0EuY2l0eShwY29hLmFpdGMsIG1ldGFfc2FtcGxlKQpwLkJSQS5jaXR5IDwtIHBsb3QuUENPQS5jaXR5KHBjb2EuYnJheSwgbWV0YV9zYW1wbGUpCmBgYAoKCmBgYHtyLCBmaWcud2lkdGg9MjAsIGZpZy5oZWlnaHQ9NH0KcC5wc2VvIDwtIGdncHVicjo6Z2dhcnJhbmdlKAogIHAuSlNELnBzZW8gKyBnZ3RpdGxlKCJqZW5zZW4tc2hhbm5vbiBwc2V1ZG9tb25hZGFjZWFlIiksIAogIHAuQ0FOLnBzZW8gKyBnZ3RpdGxlKCJjYW5iZXJyYSBwc2V1ZG9tb25hZGFjZWFlIiksIAogIHAuSEVMLnBzZW8gKyBnZ3RpdGxlKCJoZWxsaW5nZXIgcHNldWRvbW9uYWRhY2VhZSIpLAogIHAuQlJBLnBzZW8gKyBnZ3RpdGxlKCJicmF5LWN1cnRpcyBwc2V1ZG9tb25hZGFjZWFlIiksCiAgcC5BSVQucHNlbyArIGdndGl0bGUoImFpdGNoaXNvbiBwc2V1ZG9tb25hZGFjZWFlIiksIAogIG5jb2wgPSA1LCBsZWdlbmQgPSAibm9uZSIKKQpgYGAKCmBgYHtyLCBmaWcud2lkdGg9MjAsIGZpZy5oZWlnaHQ9NH0KcC5jaXR5IDwtIGdncHVicjo6Z2dhcnJhbmdlKAogIHAuSlNELmNpdHkgKyBnZ3RpdGxlKCJqZW5zZW4tc2hhbm5vbiBjaXR5IiksIAogIHAuQ0FOLmNpdHkgKyBnZ3RpdGxlKCJjYW5iZXJyYSBjaXR5IiksIAogIHAuSEVMLmNpdHkgKyBnZ3RpdGxlKCJoZWxsaW5nZXIgY2l0eSIpLAogIHAuQlJBLmNpdHkgKyBnZ3RpdGxlKCJicmF5LWN1cnRpcyBjaXR5IiksCiAgcC5BSVQuY2l0eSArIGdndGl0bGUoImFpdGNoaXNvbiBjaXR5IiksIAogIG5jb2wgPSA1LCBjb21tb24ubGVnZW5kID0gVCwgbGVnZW5kID0gIm5vbmUiCikKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTIwLCBmaWcuaGVpZ2h0PTh9CnBhbGwgPC0gZ2dwdWJyOjpnZ2FycmFuZ2UocC5wc2VvLCBwLmNpdHksIG5yb3cgPSAyKQpwYWxsCmBgYAoKYGBge3J9CnBuZygiLi4vU3VwcGxlbWVudGFyeS9FV0FERVNfcGNvYV9tb3JlX21ldHJpY3MucG5nIiwgd2lkdGggPSA2MDAwLCBoZWlnaHQgPSAyNDAwLCByZXMgPSAzMDApCnBhbGwKZGV2Lm9mZigpCmBgYAoK